#include <linux/module.h>
#include <linux/init.h>
#include <linux/gfp.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/vfs.h>
#include <linux/pagemap.h>
#include <linux/highmem.h>
#include <linux/string.h>

#define YESNO_MAGIC 232535

int levels = 5;		/*the max depth of the our file system */

static struct inode *yesno_get_inode(struct super_block *, int);

static struct dentry *yesno_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)	/*this will be called when the kernel is resolving a path*/
{	
	struct inode *inode_new;          
	
	if(strcmp(dentry->d_name.name,"yes") == 0 && levels > *((int*)dir->i_private))
	{
		inode_new = yesno_get_inode(dir->i_sb, dir->i_ino);
		*((int*)inode_new->i_private) = *((int*)dir->i_private) + 1;
		d_add(dentry, inode_new);
	}
	
	if(strcmp(dentry->d_name.name,"no") == 0 && levels > *((int*)dir->i_private))
	{
		inode_new = yesno_get_inode(dir->i_sb, dir->i_ino);
		*((int*)inode_new->i_private) = *((int*)dir->i_private) + 1;
		d_add(dentry, inode_new);
	}	
	if(strcmp(dentry->d_name.name,"donno") == 0 && (levels > *((int*)dir->i_private)))
	{
		inode_new = yesno_get_inode(dir->i_sb, dir->i_ino);
		*((int*)inode_new->i_private) = *((int*)dir->i_private) + 1;
		d_add(dentry, inode_new);
	}
	return NULL;
}

static const struct inode_operations yesno_dir_inode_operations = {
	.lookup		= yesno_lookup,
};

static int yesno_readdir(struct file *filp, void *dirent, filldir_t filldir)	/*this will be called when the kernel wants the contents of a directory*/
{
	struct dentry *de = filp->f_dentry;

	if(filp->f_pos > 0 )
		return 1;
	if (levels > *((int *)de->d_inode->i_private)) {
		if(filldir(dirent, ".", 1, filp->f_pos++, de->d_inode->i_ino, DT_DIR)||
			(filldir(dirent, "..", 2, filp->f_pos++, de->d_parent->d_inode->i_ino, DT_DIR)))
			return 0;
		if(filldir(dirent, "yes", 3, filp->f_pos++, de->d_inode->i_ino, DT_REG))
			return 0;
		if(filldir(dirent, "no", 2, filp->f_pos++, de->d_inode->i_ino, DT_REG))
			return 0;
		if(filldir(dirent, "donno", 5, filp->f_pos++, de->d_inode->i_ino, DT_REG))
			return 0;
	}
	
	return 2;
}

static const struct file_operations yesno_dir_operations = {
	.readdir =	yesno_readdir,
	.owner =	THIS_MODULE,
	.read = 	generic_read_dir,
};

struct inode *yesno_get_inode(struct super_block *sb, int num)
{
	struct inode *inode = new_inode(sb);

	if (inode) {
		inode->i_mode = S_IFDIR | 0555;
		inode->i_uid = 0;
		inode->i_gid = 0;
		inode->i_blocks = 0;
		inode->i_op = &yesno_dir_inode_operations;
		inode->i_fop = &yesno_dir_operations;
		inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
		inc_nlink(inode);
		inode->i_ino = num;
		inode->i_private = (int*)kmalloc(sizeof(int), GFP_KERNEL);		/*field stores the current depth*/
		*((int*)inode->i_private) = 0;
	}
	return inode;
}

static struct super_operations yesno_ops = {
	.statfs 	= simple_statfs,
};

static int yesno_fill_super(struct super_block *sb, void *data, int silent)
{
	struct inode *inode;
	struct dentry *root;

	sb->s_maxbytes = MAX_LFS_FILESIZE;
	sb->s_blocksize = PAGE_CACHE_SIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	sb->s_magic = YESNO_MAGIC;
	sb->s_op = &yesno_ops;
	sb->s_time_gran = 1;
	inode = yesno_get_inode(sb, 1);
	if (!inode)
		return -ENOMEM;
	root = d_make_root(inode);
	if (!root) {
		iput(inode);
		return -ENOMEM;
	}
	sb->s_root = root;
	return 0;
}

static struct dentry *yesno_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
{
	return mount_nodev(fs_type, flags, data, yesno_fill_super);
}

static struct file_system_type yesno_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "tri_fs",
	.mount		= yesno_mount,
	.kill_sb	= kill_anon_super,
};

static int __init init_tri_fs(void)
{
	return register_filesystem(&yesno_fs_type);
}

static void __exit exit_tri_fs(void)
{
	unregister_filesystem(&yesno_fs_type);
}

module_init(init_tri_fs)
module_exit(exit_tri_fs)